package com.intertid.mongodb

import scala.reflect.BeanProperty
import com.mongodb.Mongo
import com.mongodb.casbah.MongoConnection
import com.intertid.mongodb.commons.MongoDBUtils
import com.intertid.mongodb.error.MongoDBException
import com.intertid.mongodb.error.MongoDBErrorDefine
import com.mongodb.casbah.MongoDB
import net.sf.cglib.proxy.MethodInterceptor
import com.intertid.scala.commons._
import com.mongodb.casbah.query.dsl._
import com.mongodb.casbah.query._
import org.apache.commons.beanutils.PropertyUtils
import com.intertid.mongodb.annotations.Ref
import com.intertid.mongodb.annotations.Transient
import java.util.Collection
import factory.MongoDBTemplateFactory
import org.bson.types.ObjectId

class MongoDBTemplate(mongo: Mongo, dbName: String) {

  def this() = this(new Mongo(), "test");

  @BeanProperty
  var connection = new MongoConnection(mongo);

  @BeanProperty
  var session = connection(dbName);

  def apply(name: String) = session(name);

  def release() = {
    connection.close();
  }

  def init() = {
    MongoDBTemplateFactory.instance = this;
  }

  private def getBeanCollectionName(bean: Object): String = {
    val cls = bean.getClass();

    val collectionName = if (!Mapper.exists(cls)) {
      throw new MongoDBException(MongoDBErrorDefine.ENTITY_DEFINE_NOT_FOUND);
    } else {
      Mapper.getCollectionName(cls)
    }

    collectionName;
  }

  def save(bean: Object): Object = {
    val collectionName = getBeanCollectionName(bean);
    val dbobj = MongoDBUtils.bean2DBObject(bean);

    val result = session(collectionName) += dbobj;

    val propDesc = MongoDBUtils.findIDProperty(bean.getClass().getRealClass());
    if (propDesc.getPropertyType().equals(classOf[String])) {
      propDesc.getWriteMethod().invoke(bean, dbobj.get(MongoDBUtils.MONGODB_PK_NAME).toString());
    } else {
      propDesc.getWriteMethod().invoke(bean, dbobj.get(MongoDBUtils.MONGODB_PK_NAME));
    }

    bean;
  }

  def saves(beans: Object*): Unit = {
    beans.foreach(b => {
      save(b)
    });
  }

  def +=(bean: Object): Object = save(bean);

  def *=(beans: Object*): Unit = saves(beans: _*);

  private def cascadesDelete(bean: Object): Unit = {

  }

  def delete(bean: Object): Unit = {

    val deletes = getCascades(bean);

    deletes.foreach(d => {
      val collectionName = getBeanCollectionName(d);
      val collection = session(collectionName);

      val idProp = MongoDBUtils.findIDProperty(d.getClass().getRealClass());
      if (idProp == null) dberror(MongoDBErrorDefine.ID_NOT_FOUND);
      val id = idProp.getReadMethod().invoke(d);
      if (id != null) {
        session(collectionName) -= (empty + ("_id" -> (if (id.isInstanceOf[String]) new ObjectId(id.toString()) else id)));
      }

    })
    val collectionName = getBeanCollectionName(bean);
    val collection = session(collectionName);

    val idProp = MongoDBUtils.findIDProperty(bean.getClass().getRealClass());
    if (idProp == null) dberror(MongoDBErrorDefine.ID_NOT_FOUND);
    val id = idProp.getReadMethod().invoke(bean);
    if (id != null) {
      collection -= (empty + ("_id" -> (if (id.isInstanceOf[String]) new ObjectId(id.toString()) else id)));
    }

    collection -= empty + ("_id" -> (if (id.isInstanceOf[String]) new ObjectId(id.toString()) else id));
  }

  def deletes(beans: Object*): Unit = {
    beans.foreach(b => {
      beans.foreach(b => {
        delete(b);
      })
    })
  }

  def -=(bean: Object) = delete(bean);

  def /=(beans: Object*) = deletes(beans: _*);

  private def getCascades(bean: Object): List[Object] = {
    if (bean == null) return Nil;
    val classz = bean.getClass().getRealClass();
    val props = PropertyUtils.getPropertyDescriptors(classz);
    var result: List[Object] = Nil;

    if (!Mapper.exists(classz))
      return result;
    props.foreach(p => {
      try {

        val f = classz.getDeclaredField(p.getName());

        if (!f.isAnnotationPresent(classOf[Transient])) {

          val value = p.getReadMethod().invoke(bean)

          if (value != null) {

            if (f.isAnnotationPresent(classOf[Ref])) {

              val ref = f.getAnnotation(classOf[Ref]);
              if (ref.cascadeDelete()) {
                if (value.isInstanceOf[Collection[_]]) {

                  value.asInstanceOf[Collection[_]].foreach(v => {
                    result = result ::: List[Object](v.asInstanceOf[Object]);

                  })
                } else if (value.isInstanceOf[scala.List[_]]) {

                  value.asInstanceOf[scala.List[_]].foreach(v => {
                    result = result ::: List[Object](v.asInstanceOf[Object]);
                  })
                } else if (value.getClass().isArray()) {
                  value.asInstanceOf[Array[_]].foreach(v => {
                    result = result ::: List[Object](v.asInstanceOf[Object]);
                  })
                } else {
                  result = result ::: List[Object](value);
                }
              }
            }
            if (value.isInstanceOf[Collection[_]]) {
              value.asInstanceOf[Collection[_]].foreach(v => {
                result = result ::: getCascades(v.asInstanceOf[Object]);

              })
            } else if (value.isInstanceOf[scala.List[_]]) {

              value.asInstanceOf[scala.List[_]].foreach(v => {
                result = result ::: getCascades(v.asInstanceOf[Object]);
              })
            } else if (value.getClass().isArray()) {
              value.asInstanceOf[Array[_]].foreach(v => {
                result = result ::: getCascades(v.asInstanceOf[Object]);
              })
            } else {
              result = result ::: getCascades(value);
            }

          }
        }
      } catch {
        case e: NoSuchFieldException => {

        }
      }
    })

    return result;
  }
}



